package com.tools.web.interceptor.auth.jwt;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.tools.common.object.Note;
import com.tools.common.thread.Timeunit;
import com.tools.web.interceptor.auth.GenericAuthToken;
import com.tools.web.interceptor.auth.LoginDeviceType;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 使用 JWT 实现的 AuthToken
 * */
@Note("使用 JWT 实现的 AuthToken")
public final class JWTAuthToken extends GenericAuthToken {

    @Note("JWT 的加密算法对象。可以通过 Algorithm.xxx 获取，同时可以设置为单例复用")
    private final Algorithm algorithm;

    /* **************************************************************************************
     *
     *          构造器
     *
     * **************************************************************************************
     * */

    public JWTAuthToken(Algorithm algorithm) {
        if (algorithm == null) throw new NullPointerException("JWTAuthToken 的加密工具实例不能为 null");
        this.algorithm = algorithm;
    }

    public JWTAuthToken(Algorithm algorithm, String encryptedToken) {
        this(algorithm);
        this.decrypt(encryptedToken);
    }

    /* **************************************************************************************
     *
     *          实现的功能方法
     *
     * **************************************************************************************
     * */


    @Override
    public String encrypt() {
        super.protected_checkExpiration();
        Map<String, Object> header = new HashMap<>(4);
        header.put("typ", "JWT");
        header.put("alg", this.algorithm.getName());
        long loginTimeMill = super.getLoginTimeMill();
        long expirationMill = super.getExpirationMill();
        JWTCreator.Builder builder = JWT.create()
                .withHeader(header)
                .withSubject("Access JWTAuthToken")
                .withClaim("uuid", super.getUuid());
        return this.private_claimPutUserId(builder)
                .withClaim("loginDeviceType", Objects.requireNonNull(super.getLoginDeviceType(), "AuthToken 的客户端登录设备类型不能为空"))
                .withClaim("loginUserType", Objects.requireNonNull(super.getLoginUserType(), "AuthToken 的登录用户的类型标字符串标识不能为空"))
                .withClaim("loginTime", loginTimeMill)
                .withClaim("extendedCount", super.getExtendedCount())
                .withClaim("expiration", expirationMill)
                .withIssuedAt(new Date(loginTimeMill))
                .withExpiresAt(new Date(expirationMill))
                .sign(this.algorithm);
    }


    @Override
    public void decrypt(String encryptedToken) {
        if(encryptedToken.startsWith("Bearer ")) {
            encryptedToken = encryptedToken.substring(7);
        }
        JWTVerifier jwtVerifier = JWT.require(this.algorithm).build();
        DecodedJWT jwt = jwtVerifier.verify(encryptedToken);
        super.setUuid(jwt.getClaim("uuid").asString());
        this.private_getUserIdFromClaim(jwt);
        super.setLoginDeviceType(jwt.getClaim("loginDeviceType").asString());
        super.setLoginUserType(jwt.getClaim("loginUserType").asString());
        super.setLoginTimeMill(jwt.getClaim("loginTime").asLong(), Timeunit.MILL);
        super.setExtendedCount(jwt.getClaim("extendedCount").asInt());
        super.setExpirationMill(jwt.getClaim("expiration").asLong(), Timeunit.MILL);
    }

    /* **************************************************************************************
     *
     *          链式调用
     *
     * **************************************************************************************
     * */

    @Override
    public JWTAuthToken uuid(String uuid) {
        super.uuid(uuid);
        return this;
    }

    @Override
    public JWTAuthToken userId(Object userId) {
        super.userId(userId);
        return this;
    }

    @Override
    public JWTAuthToken userId(String userId) {
        super.userId(userId);
        return this;
    }

    @Override
    public JWTAuthToken userId(long userId) {
        super.userId(userId);
        return this;
    }

    @Override
    public JWTAuthToken userId(int userId) {
        super.userId(userId);
        return this;
    }

    @Override
    public JWTAuthToken loginDeviceType(String loginDeviceType) {
        super.loginDeviceType(loginDeviceType);
        return this;
    }

    @Override
    public JWTAuthToken loginDeviceType(LoginDeviceType loginDeviceType) {
        super.loginDeviceType(loginDeviceType);
        return this;
    }

    @Override
    public JWTAuthToken loginUserType(String loginUserType) {
        super.loginUserType(loginUserType);
        return this;
    }

    @Override
    public JWTAuthToken loginTimeMill(long loginTime, Timeunit loginTimeunit) {
        super.loginTimeMill(loginTime, loginTimeunit);
        return this;
    }

    @Override
    public JWTAuthToken expirationMill(long expiration, Timeunit expirationTimeunit) {
        super.expirationMill(expiration, expirationTimeunit);
        return this;
    }

    @Override
    public JWTAuthToken extendedCount(int extendedCount) {
        super.extendedCount(extendedCount);
        return this;
    }

    /* **************************************************************************************
     *
     *          私有逻辑
     *
     * **************************************************************************************
     * */

    @Note("往 JWT 的 Claim 里加入 userId 的值")
    private JWTCreator.Builder private_claimPutUserId(JWTCreator.Builder builder) {
        Object userId = super.getUserId();
        if (userId instanceof Long) {
            return builder.withClaim("userIdType", 0).withClaim("userId", (Long) userId);
        }
        if (userId instanceof String) {
            return builder.withClaim("userIdType", 1).withClaim("userId", (String) userId);
        }
        return builder.withClaim("userIdType", 2).withClaim("userId", (Integer) userId);
    }


    @Note("从 JWT 的 Claim 获取用户 ID 后进行设置")
    private void private_getUserIdFromClaim(DecodedJWT jwt) {
        int userIdType = jwt.getClaim("userIdType").asInt();
        switch (userIdType) {
            case 0: {
                long userId = jwt.getClaim("userId").asLong();
                super.setUserId(userId);
                return;
            }
            case 1: {
                String userId = jwt.getClaim("userId").asString();
                super.setUserId(userId);
                return;
            }
            default: {
                int userId = jwt.getClaim("userId").asInt();
                super.setUserId(userId);
            }
        }
    }
}
